#pragma once

using std::cout;
using std::endl;

struct Vertex {
    Vertex* pred;
    char array[16];
    int g;
    int h;
    long long hash;
    char changed;

    Vertex():pred(0), g(0), h(0), hash(0), changed(0) {}

    bool operator==(const Vertex& v) const { return hash == v.hash; };

    bool operator!=(const Vertex& v) const { return !(operator==(v)); }

    bool operator<(const Vertex& v) const { return hash < v.hash; }

    Vertex(const Vertex& v): pred(0), g(0), h(0), hash(0) {
        for (size_t i = 0; i < 16; ++i) { array[i] = v.array[i]; }
    }

    int setH();

    int getF() const { return h + g; }

    void print() const;

    void calculateConflicts();
    void calculateManhattanDistance();
    void calculateCornerTiles();
    void calculateLastMove();

    void setHash() {
        const int p = 17;
        long long p_pow = 1;
        for (size_t i = 0; i < 15; ++i) {
            hash += (array[i] + 1) * p_pow;
            p_pow *= p;
        }
    }

};

int Vertex::setH() {
    h = 0;
    calculateManhattanDistance();
    calculateConflicts();
    calculateCornerTiles();
    calculateLastMove();
    return h;
}

void Vertex::print() const {
    for (size_t i = 0; i < 4; ++i) {
        for (size_t j = 0; j < 4; ++j) {
            cout<<(short)array[j + i*4]<<"\t";
        }
        cout<<endl;
    }
    cout<<endl;
}

void Vertex::calculateConflicts() {
    short a = 0 , b = 0;

    for (size_t i = 0; i < 4; ++i) {
        for (size_t j = 0; j < 3; ++j) {
            for (size_t k = j + 1; k < 4; ++k) {
                a = (array[j + i*4] - 1) / 4;
                b = (array[k + i*4] - 1) / 4;
                if ((a == b) && (b == i) && (array[j + i*4] > array[k + i*4]) && (array[k + i*4] > 0)) { h += 2; }

                a = (array[i + j*4] - 1) % 4;
                b = (array[i + k*4] - 1) % 4;
                if ((a == b) && (b == i) && (array[i + j*4] > array[i + k*4]) && (array[i + k*4] > 0)) { h += 2; }
            }
        }
    }
}

void Vertex::calculateManhattanDistance() {
    short a = 0 , b = 0;
    for (size_t i = 0; i < 4; ++i){
        for (size_t j = 0; j < 4; ++j) {
            if (array[j + i*4] != 0) {
                a = (array[j + i*4] - 1) / 4 ;
                b = (array[j + i*4] - 1) % 4;
                h += abs(i - a) + abs(j - b);
            }
        }
    }
}

void Vertex::calculateCornerTiles() {
    if (array[2] == 3 && array[7] == 8 && array[3] != 4) {
        if (array[3] != 1 && array[3] != 2 && array[3] != 12 && array[3] != 0) { h += 2; }
    }

    if (array[8] == 9 && array[13] == 14 && array[12] != 13) {
        if (array[12] != 1 && array[12] != 5 && array[12] != 15 && array[12] != 0) { h += 2; }
    }

    if (array[1] == 2 && array[4] == 5 && array[0] != 1) {
        if (array[0] != 3 && array[0] != 4 && array[0] != 9 && array[0] != 13) { h += 2; }
    }
}

void Vertex::calculateLastMove() {
    h += 4;
    for (size_t i = 0; i < 4; ++i) {
        if (array[4*3 + i] == 15) { h -= 2; }
        if (array[4*i + 3] == 12) { h -= 2; }
    }
}
